#include "APICamion.h"


APICamion::APICamion() {
	// Obtengo todos los IPCs dado que no sé cuales voy a utilizar
	
	this->semControl = Semaphore();
	this->semControl.getSemaphore((char *) DIRECTORY, ID_SEMCONTROL, 1);
	this->shMem = ControlDataSharedMemory();
	this->shMem.getSharedMemory((char *) DIRECTORY, ID_SHMEM);
	
	this->semTruckDoor = Semaphore();
	this->semTruckDoor.getSemaphore((char *) DIRECTORY, ID_SEMTRUCKDOOR, CANT_DOORS);
	
	this->semTruck = Semaphore();
	this->semTruck.getSemaphore((char *) DIRECTORY, ID_SEMTRUCK, CANT_TRUCKS);

	// Obtención de colas para la carga de personas al camión
	this->truckChargeInputQueue = TruckInputMessageQueue();
	this->truckChargeInputQueue.getMessageQueue((char *) DIRECTORY, ID_Q_TRUCK_CHARGE_INPUT);
	this->truckChargeOutputQueue = TruckOutputMessageQueue();
	this->truckChargeOutputQueue.getMessageQueue((char *) DIRECTORY, ID_Q_TRUCK_CHARGE_OUTPUT);
	
	// Obtención de colas para la descarga de personas al camión
	this->truckDischargeInputQueue = TruckInputMessageQueue();
	this->truckDischargeInputQueue.getMessageQueue((char *) DIRECTORY, ID_Q_TRUCK_DISCHARGE_INPUT);
	this->truckDischargeOutputQueue = TruckOutputMessageQueue();
	this->truckDischargeOutputQueue.getMessageQueue((char *) DIRECTORY, ID_Q_TRUCK_DISCHARGE_OUTPUT);
	
	// 
	this->chargeToTruckQueue = ChargeToTruckMessageQueue();
	this->chargeToTruckQueue.getMessageQueue((char *) DIRECTORY, ID_Q_CHARGE_TO_TRUCK);

}

APICamion::~APICamion() {
}


/* - Primero se accede a la memoria compartida e incrementa en su capacidad, 
 *   la capacidad de la sala. 
 * - Luego verifica si hay algún camión cargando. En caso afirmativo, el camión
 *   se bloquea. 
 * - Luego, tanto en el caso de que el camión haya sido desbloqueado como en el 
 *   caso de que no haya otro camión en la sala, el camión toma la sala.
 * - Luego verifica si la capacidad de la sala era 0. En este caso, desbloquea a
 *   las puertas que estuvieran bloquedas. 
 * - Por último, libera la memoria compartida.
 */
void APICamion::ingresarEnSala(int nroCamion, int *error) {
	ControlData aControlData;
	
	this->accederAMemoriaCompartida(nroCamion, error);
	if (*error != 0) return;
	
	this->shMem.read(&aControlData);
	
	#ifdef _DEBUG
	sprintf(buffer, "Camion %d: CPS: %d - CES: %d - CS: %d - CPC: %d\n", 
	nroCamion, aControlData.cantidadDePersonas, aControlData.camionEnSala, 
	aControlData.capacidadSala, aControlData.cantidadPersonasEnCamion);
	write(fileno(stdout), buffer, strlen(buffer));
	#endif
	
	aControlData.capacidadSala += TRUCK_CAPACITY;
	
	if (aControlData.camionEnSala != 0) {
		// La sala está ocupada, el camión se bloquea
		agregarCamion(&(aControlData.camionesBloqueados), nroCamion);
		this->shMem.write(&aControlData);
		
		this->liberarMemoriaCompartida(nroCamion, error);
		if (*error != 0) return;
	
		sprintf(buffer, "Camion %d: Procede a bloquearse\n", nroCamion);
		write(fileno(stdout), buffer, strlen(buffer));
		if (this->semTruck.wait(nroCamion-1) == -1) {
			*error = ZIPC_FAILURE;
			return;
		}		
		
		// El camión fue desbloqueado, el mismo puede entrar a la sala
		this->accederAMemoriaCompartida(nroCamion, error);
		if (*error != 0) return;
		this->shMem.read(&aControlData);
		
		#ifdef _DEBUG
		sprintf(buffer, "Camion %d: CPS: %d - CES: %d - CS: %d - CPC: %d\n", 
		nroCamion, aControlData.cantidadDePersonas, aControlData.camionEnSala, 
		aControlData.capacidadSala, aControlData.cantidadPersonasEnCamion);
		write(fileno(stdout), buffer, strlen(buffer));
		#endif

	}
		
	// No hay camión en la sala, tomo a la misma
	aControlData.camionEnSala = nroCamion;
		
	#ifdef _DEBUG
	sprintf(buffer, "Camion %d: Ingresa en sala\n", nroCamion);
	write(fileno(stdout), buffer, strlen(buffer));
	#endif
	
	// Libero a las puertas que pudieran estar bloqueadas
	if (aControlData.cantidadDePersonas == 0) {
		this->desbloquearPuertas(nroCamion, aControlData.puertaCamionBloqueada, error);
		if (*error != 0) return;
	}
	
	// Se escribe y libera la memoria compartida 
	this->shMem.write(&aControlData);
	this->liberarMemoriaCompartida(nroCamion, error);
}

void APICamion::abandonarSala(int nroCamion, int *error) {
	ControlData aControlData;
	
	this->accederAMemoriaCompartida(nroCamion, error);
	if (*error != 0) return;
	
	this->shMem.read(&aControlData);
	this->liberarSala(nroCamion, &aControlData, error);
	
	this->shMem.write(&aControlData);
	this->liberarMemoriaCompartida(nroCamion, error);
}

/* - Primero se espera a que llegue una persona en la cola entre el camión y 
 *   la puerta
 * - Luego se accede a la memoria compartida y se decrementa en 1 la cantidad
 *   de personas que hay en la sala.
 * - Por último, se verifica si el camión debe abandonar la sala (no hay más
 *   personas en la misma). En este caso se libera, en el caso que haya, a
 *   otro camión para que comience a cargar personas.
 */
long APICamion::subirPersona(int nroCamion, bool *salaLiberada, int *error) {
	ControlData aControlData;
	long idPersona;
	
	// Se obtiene el ID de la persona, y luego se realiza el intercambio de 
	// mensajes necesario para que la misma suba al camión
	idPersona = obtenerIdPersonaASubir(nroCamion, error);
	if (*error != 0) return 0;
	
	handshakeConPersonaASubirACamion(nroCamion, idPersona, error);
	if (*error != 0) return 0;
	
	// Luego de haber subido a la persona, se procede a registrar esto en la 
	// memoria compartida.
	this->accederAMemoriaCompartida(nroCamion, error);
	if (*error != 0) return 0;
	this->shMem.read(&aControlData);
	
	#ifdef _DEBUG
	sprintf(buffer, "Camion %d: CPS: %d - CES: %d - CS: %d - CPC: %d\n", 
	nroCamion, aControlData.cantidadDePersonas, aControlData.camionEnSala, 
	aControlData.capacidadSala, aControlData.cantidadPersonasEnCamion);
	write(fileno(stdout), buffer, strlen(buffer));
	#endif
	
	aControlData.cantidadDePersonas--;
	aControlData.cantidadPersonasEnCamion++;
	
	
	if (aControlData.cantidadDePersonas == 0) {
		this->liberarSala(nroCamion, &aControlData, error);
		if (*error != 0) return 0;
		*salaLiberada = true;
	}
	
	// Se escribe la memoria compartida y se libera a la misa
	this->shMem.write(&aControlData);
	this->liberarMemoriaCompartida(nroCamion, error);
	return idPersona;
}


void APICamion::bajarPersona(int nroCamion, long idPersona, int *error) {
	TruckInputMsg inputMsg;
	TruckOutputMsg outputMsg;
	
	// Armo el mensaje y lo envío
	outputMsg.mtype = idPersona;
	outputMsg.nroCamion = nroCamion;
	if (this->truckDischargeOutputQueue.enviarMensaje(outputMsg) == -1) {
		*error = ZIPC_FAILURE;
		return;
	}
	
	// Espero la respuesta de la persona
	if (this->truckDischargeInputQueue.recibirMensaje(nroCamion, &inputMsg) == -1) {
		*error = ZIPC_FAILURE;
		return;
	}
	
}

/******************************************************************************/
// Métodos privados

void APICamion::desbloquearPuertas(int nroCamion, bool array[], int* error) {
	for (int i = 0; i < CANT_DOORS; i++) {
		if (array[i] == true) {
			array[i] = false;
			sprintf(buffer, "Camion %d: Libera a PuertaCamion %d\n", nroCamion, i+1);
			write(fileno(stdout), buffer, strlen(buffer));
		
	
			if (this->semTruckDoor.signal(i) == -1) {
				*error = ZIPC_FAILURE;
				return;
			}
		}
	}
}	
	
long APICamion::obtenerIdPersonaASubir(int nroCamion, int *error) {
	ChargeToTruckMsg personaACargar;
	
	if (this->chargeToTruckQueue.recibirMensaje(nroCamion, &personaACargar) == -1) {
		*error = ZIPC_FAILURE;
	}
	return personaACargar.idPersona;
}

void APICamion::handshakeConPersonaASubirACamion(int nroCamion, long idPersona, int *error) {
	TruckInputMsg inputMsg;
	TruckOutputMsg outputMsg;
	
	// Armo el mensaje y lo envío
	outputMsg.mtype = idPersona;
	outputMsg.nroCamion = nroCamion;
	if (this->truckChargeOutputQueue.enviarMensaje(outputMsg) == -1) {
		*error = ZIPC_FAILURE;
		return;
	}
	
	// Espero la respuesta de la persona
	if (this->truckChargeInputQueue.recibirMensaje(nroCamion, &inputMsg) == -1) {
		*error = ZIPC_FAILURE;
		return;
	}
}
	

void APICamion::accederAMemoriaCompartida(int id, int *error) {
	if (this->semControl.wait() == -1) {	
		*error = ZIPC_FAILURE;
		return;
	}
	/*sprintf(buffer, "Camion %d: Accede la memoria compartida\n", id);
	write(fileno(stdout), buffer, strlen(buffer));*/
}


void APICamion::liberarMemoriaCompartida(int id, int *error) {
	/*sprintf(buffer, "Camion %d: Libera memoria compartida\n", id);
	write(fileno(stdout), buffer, strlen(buffer));	*/
	if (this->semControl.signal() == -1) {
		*error = ZIPC_FAILURE;
		return;
	}
}

void APICamion::liberarSala(int nroCamion, ControlData *data, int *error) {
	#ifdef _DEBUG
	sprintf(buffer, "Camion %d: Libera sala\n", nroCamion);
	write(fileno(stdout), buffer, strlen(buffer));
	#endif
	
	data->camionEnSala = 0;
	data->capacidadSala -= (TRUCK_CAPACITY - data->cantidadPersonasEnCamion);
	data->cantidadPersonasEnCamion = 0;
	
	if (getCantidadCamionesBloqueados(&(data->camionesBloqueados)) != 0) {
		int camionADesbloquear = obtenerUltimoCamionBloqueado(&(data->camionesBloqueados));
				
		sprintf(buffer, "Camion %d: Desbloquea a camion %d\n", nroCamion, camionADesbloquear);
		write(fileno(stdout), buffer, strlen(buffer));
		
		if (this->semTruck.signal(camionADesbloquear -1) == -1) {
			*error = ZIPC_FAILURE;
			return;
		}
	}
}
